<template>
  <div class="call-topology" :style="containerStyle">
    <section class="flex-spb-center" style="margin-bottom: 8px;">
      <div>
        <ga-select placeholder="选择拓扑节点名称搜索" style="width: 210px; margin-right: 8px" allowClear showArrow showSearch
          @change="onSearchNode" @clear="() => onSearchNode('')">
          <ga-option :value="node.id" :label="node.nameBak || node.workloadName" :key="node.id"
            v-for="node in topoNodesToSearch">
            <div :style="{
              ...statusDotStyle,
              background: statusColorMap[getNodeAndEdgeStatus(node)],
            }"></div>
            {{ node.nameBak || node.workloadName }}
          </ga-option>
        </ga-select>
        <ga-select :model-value="groupValue" :options="groupOptions" @change="changeGroupValue"
          style="width: 150px; margin-right: 8px" />

        <ga-select placeholder="指标（分钟级）" style="width: 120px; margin-right: 8px"  showArrow showSearch
          v-model="dataFilter.field">
          <ga-option :value="field.value" :label="field.label" :key="field.value" v-for="field in filters.fields">
            {{ field.label }}
          </ga-option>
        </ga-select>
        <ga-select style="width: 110px; margin-right: 8px" showArrow showSearch v-model="dataFilter.count">
          <ga-option :value="count.value" :label="count.label" :key="count.value" v-for="count in filters.count">
            {{ count.label }}
          </ga-option>
        </ga-select>
      </div>
      <ga-space v-show="mode === ''">
        <ga-button @click="changeAttachDataType">
          {{ attachDataTypeText }}
        </ga-button>
        <ga-button type="primary" @click="onMerge"
          :disabled="groupValue === 'namespace' || $router.systemType === '0'">归并</ga-button>

        <ga-button type="primary" :disabled="$route.query.type === '0' || traceBackConfig.isTraceBack"
          @click="onEditSystem">编辑系统</ga-button>
      </ga-space>
    </section>

    <!-- <section class="topo-main">

    </section> -->
    <div class="topo-wrapper">
      <section class="operation-items flex-spb-center" :style="mode ? { justifyContent: 'flex-end' } : {}">
        <ga-space>


          <ga-radio-group type="button" v-model="nodeMode">
            <ga-radio value="rect">方形节点</ga-radio>
            <ga-radio value="circle">圆形节点</ga-radio>

          </ga-radio-group>
        </ga-space>
        <ga-space>
          <span v-show="!mode && !traceBackConfig.isTraceBack">
            <ga-switch :checked="systemInfo.autoSync ? true : false" @change="updateAutoSync"
              :loading="syncEntranceConfig.loading" />
            <span class="ml6 mr12">自动同步入口节点</span>

          </span>
          <ga-button :loading="traceBackConfig.loading" :disabled="mode ? true : false" @click="onTriggerTrackBack"
            style="padding: 8px;">
            <!-- {{ traceBackConfig.isTraceBack ? "关闭回溯" : "回溯" }} -->
            <ga-tooltip content="根据时间段回溯并展示拓扑图变化过程">

              <img src="@/assets/images/topology/replay.png" style="width: 16px; height: 16px;" />
            </ga-tooltip>
          </ga-button>
          <ga-button @click="() => (isFullScreen = !isFullScreen)" class="p8">
            <icon-fullscreen-exit v-show="isFullScreen" />
            <icon-fullscreen v-show="!isFullScreen" />
          </ga-button>
          <ga-button @click="onSaveTopoChange" :disabled="groupValue === 'namespace' || $route.query.type === '0'"
            class="p8"> <img src="@/assets/images/topology/save-topo.png"
              style="width: 16px; height: 16px;" /></ga-button>
          <ga-button type="primary" style="padding: 8px;" @mouseenter="() => tipVisible = true" class="tip-button">
            <icon-question />
          </ga-button>
        </ga-space>
      </section>
      <TopoTipModal :visible="tipVisible" @on-close="() => tipVisible = false"/>
      <EditTopo ref="editTopoRef" domId="editTopo" :topoData="editTopoData" :systemId="$route.query.systemId"
        :isFullScreen="isFullScreen" @on-cancel="() => (mode = '')" @on-confirm="onEditConfirm"
        @show-adjacent="showAdjacentNodes" v-if="mode === 'edit'" />
      <MergeTopo ref="mergeTopoRef" :topoData="topoData" :systemId="query.systemId" :isFullScreen="isFullScreen"
        @on-cancel="() => (mode = '')" @on-confirm="onMergeConfirm" v-else-if="mode === 'merge'" />
      <Topo :topoData="topoData" ref="topoRef" :layout="groupValue" :isFullScreen="isFullScreen"
        @update-entrance-node="updateIsEntranceNode" :dataFilter="dataFilter" :nodeMode="nodeMode" v-else />
      <div class="timer_play_warp" v-if="traceBackConfig.isTraceBack">
        <ga-spin :loading="traceBackConfig.loading">
          <time-player :trackBackData="trackBackData" @on-time-change="onTimeChange" />
        </ga-spin>
      </div>
    </div>
    <ga-modal v-model:visible="adjacentState.visible" title="节点上下游拓扑" width="60%" @ok="onModalConfirm"
      @cancel="() => (adjacentState.visible = false)" :popup-container="'.system_topo_warp'" destroyOnClose
      :confirmLoading="adjacentState.loading">
      <div :style="{ height: '60vh', display: 'flex' }">
        <EditTopo :topoData="adjacentState.topoData" :systemId="$route.query.systemId" domId="adjacentTopo"
          :nowAdjacentNodeId="adjacentState.id" />
      </div>
    </ga-modal>
  </div>
</template>
<script>
export default {
  name: "CallTopology",
};
</script>
<script setup>
import { ref, computed, watch, reactive, onMounted } from "vue";
import { useRoute } from "vue-router";

import dayjs from "dayjs";
import { flow, find, cloneDeep } from "lodash";
import useOperation from "./useOperation.js";
import request from "@/utils/request";
import {
  buildApiData,
  getTextWidth,
  getNodeAndEdgeStatus,
  clearAllStats,
  graphFocusNode,
  fullScreenStyle,
} from "./topoService.js";
import { statusColorMap, statusDotStyle } from "./const.js";
import { groupBy, forEach } from "lodash";
import Topo from "./topo.vue";
import useTopology from "./useTopology.js";
import TimePlayer from "./time-player.vue";
import EditTopo from "./edit-topo.vue";
import MergeTopo from "./merge-topo.vue";
import TopoTipModal from "./topoTipModal.vue";
import { Message } from "@garco-design/web-vue";
import {
  modifyTopology,
  singleNodeTopology,
  systemInfoData,
} from "@/api/topo.js";
import { map, filter } from "lodash";
const groupValue = ref("none");
const topoRef = ref();
const editTopoRef = ref();
const mergeTopoRef = ref();
const attachDataType = ref(0); // 展示同环比数据 0-环比 1-同比
const systemInfo = ref({});
const optionsData = ref({
  chosenNs: "",
  namespaceList: [],
  chosenClusters: [],
  clusterList: [],
});
const { query } = useRoute();
const groupOptions = [
  {
    label: "不分组",
    value: "none",
  },
  {
    label: "按命名空间分组",
    value: "namespace",
  },
  {
    label: "按归并分组",
    value: "merge",
  },
];
const containerStyle = computed(() => {
  console.log(isFullScreen, "isFull");

  return isFullScreen.value ? fullScreenStyle : {};
});
const adjacentState = ref({
  visible: false,
  adjacentTopoData: [],
  loading: false,
  id: "",
});
const isMerge = ref(false);
const mode = ref(""); // 3种 '' |'edit' | 'merge'
const timeInfo = {
  from: dayjs().subtract(24, "hour").format("YYYY-MM-DD HH:mm:ss"),
  to: dayjs().format("YYYY-MM-DD HH:mm:ss"),
};
const diff = Math.floor(dayjs(timeInfo.from).diff(dayjs(timeInfo.to)) / 1000);
timeInfo.diff = diff;

// 切换分组
const changeGroupValue = (value) => {
  // const { topoData } = this.state;

  groupValue.value = value;
  const genTopoData = flow([changeTopoDataByUserPos, getTopoDataByIsMerge]);
  const topologyData = genTopoData(topoData.value);
  const data =
    value !== "none"
      ? changeTopoDataCombosByUserPos(topologyData)
      : topologyData;
  topoData.value = data;
};
const changeTopoDataCombosByUserPos = (topoData) => {
  const userData = topoDataUserPos.value;
  // const { groupValue } = this.state;
  let targetTopoData = userData.filter((group) => {
    return (
      group.groupType ==
      (groupValue.value == "none"
        ? 0
        : groupValue.value == "namespace"
          ? 1
          : groupValue.value == "merge" && 2)
    );
  })[0]?.nodePos;
  !targetTopoData && (targetTopoData = []);

  const nodePosMap = new Map();
  if (targetTopoData.length > 0 && topoData?.combos) {
    targetTopoData.forEach((item) => {
      nodePosMap.set(item.nodeKey, { xaxis: item.xaxis, yaxis: item.yaxis });
    });

    topoData.combos.forEach((item) => {
      item = nodePosMap.get(item?.id);
    });
  }
  return topoData;
};
// 获取当前系统的基本信息
const getSystemInfoData = () => {
  systemInfoData(query.systemId).then((res) => {
    systemInfo.value = res.data || {};
  });
};

const onMergeConfirm = (nodes) => {
  const resourceWorkloads = map(nodes, (node) => {
    return {
      clusterName: node.masterName,
      masterIp: node.masterIp,
      nameBak: node.nameBak,
      namespace: node.namespace,
      workloadKind: node.workloadKind,
      workloadName: node.workloadName,
      isEntrance: node.isEntrance || 0,
      merge: node.mergeLabel ? 1 : 0,
      mergeLabel: node.mergeLabel || null,
    };
  });
  const params = {
    ...systemInfo.value,
    resourceWorkloads,
  };
  request.post("/system/upsert", params).then((res) => {
    if (res.data.data) {
      Message.success("编辑成功");
      getTopoData();
      // this.setState({ editMode: '' });
      mode.value = "";
    } else {
      Message.error(res.data.msg);
    }
  });
};
const {
  getTopoDataByIsMerge,
  topoNodesToSearch,
  topoData,
  topoDataUserPos,
  editTopoData,
  getTopoData,
  changeTopoDataByUserPos,
} = useTopology({ groupValue, attachDataType, isMerge, timeInfo });
const {
  attachDataTypeText,
  syncEntranceConfig,
  isFullScreen,
  dataFilter,
  filters,
  trackBackData,
  traceBackConfig,
  nodeModes,
  nodeMode,
  tipVisible,
  updateAutoSync,
  onTriggerTrackBack,
  onSaveTopoChange,
  onMerge,
  changeAttachDataType,
} = useOperation({
  topoData,
  topoRef,
  mode,
  groupValue,
  topoDataUserPos,
  getTopoDataByIsMerge,
  attachDataType,
  getSystemInfoData,
  timeInfo,
});
console.log(topoNodesToSearch, "topoNodesToSearch", isFullScreen);

// 点击编辑系统
const onEditSystem = () => {
  const { nodes, edges } = cloneDeep(topoData.value);
  if (nodes.length === 0) {
    Message.info("暂无拓扑数据");
    return;
  }
  console.log(nodes, "nodes");

  // topoData经过绘制之后会携带上type字段表示是哪个自定义节点，编辑时自定义节点更换，需要删除type字段
  // 目前Topo组件在draw方法中已将topoData进行深拷贝，暂时不需要删除type和边的样式
  nodes.forEach((node) => {
    // delete node.type;
    node.inSystem = true;
  });
  // 删除边的样式
  // forEach(edges, (edge: any) => {
  //     delete edge.style;
  // })
  mode.value = "edit";
  editTopoData.value = { nodes, edges };
};
// 搜索节点
const onSearchNode = (value) => {
  console.log(topoRef.value?.graph, "graph");

  if (!topoRef.value?.graph) {
    return;
  }
  const graph = topoRef.value?.graph;
  // 清除所有节点状态
  if (!value) {
    clearAllStats(graph);
    return;
  }
  if (value) {
    // const { topoData } = props;
    const searchNode = find(topoData.value.nodes, { id: value });
    clearAllStats(graph);
    if (searchNode?.comboId) {
      const comboItem = graph.findById(searchNode.comboId);
      const comboModel = comboItem.getModel();
      // 若搜索的点在combo里，则展开combo
      if (comboItem && comboModel.collapsed) {
        comboItem._xxx = true;
        graph.expandCombo(comboItem);
      }
    }

    const searchNodeItem = graph.findById(searchNode?.id);

    graphFocusNode(searchNodeItem, graph, true);
    graph.setItemState(searchNodeItem, "hover", true);
  }
};
/**
 * 更新拓扑视图中节点是否为入口节点
 * @param node 节点数据
 * @param isEntrance 是否为入口节点 1-是 0-不是
 * @returns
 */
const updateIsEntranceNode = (node, isEntrance) => {
  if (!topoRef?.value?.graph) {
    return;
  }
  const params = { systemId: query.systemId };
  const data = [
    {
      isEntrance,
      masterIp: node.masterIp,
      namespace: node.namespace,
      workloadKind: node.workloadKind,
      workloadName: node.workloadName,
      nodeId: node.nodeId,
    },
  ];
  modifyEntrance(data, params).then((res) => {
    if (res.data) {
      Message.success("设置成功");
      const graph = topoRef.value.graph;
      const item = graph.findById(node.id);
      node.isEntrance = isEntrance;
      graph.updateItem(item, node);
      // 若为自动同步节点已开启，则设置入口节点之后将会自动关闭-重新请求系统详情接口
      if (systemInfo?.value.autoSync) {
        getSystemInfoData();
      }
    } else {
      Message.error("设置失败");
    }
  });
};
const onEditConfirm = () => {
  const { nodes } = editTopoData.value;
  const inSystemNodes = filter(nodes, { inSystem: true });
  const resourceWorkloads = map(inSystemNodes, (node) => {
    return {
      clusterName: node.masterName,
      masterIp: node.masterIp,
      nameBak: node.nameBak,
      namespace: node.namespace,
      workloadKind: node.workloadKind,
      workloadName: node.workloadName,
      isEntrance: node.isEntrance || 0,
      merge: node.merge,
      mergeLabel: node.mergeLabel,
    };
  });
  if (resourceWorkloads.length === 0) {
    Message.info("需保留一个节点在当前系统");
    return;
  }
  const params = {
    ...systemInfo.value,
    resourceWorkloads,
  };
  modifyTopology(params).then((res) => {
    if (res.data) {
      Message.success("编辑成功");
      getTopoData();
      // 若为自动同步节点已开启，重新请求系统详情接口-获取当前autoSync状态
      if (systemInfo.value?.autoSync) {
        getSystemInfoData();
      }
      mode.value = "";
      // this.setState({ editMode: '' });
    } else {
      Message.error(res.data.msg);
    }
  });
};
// 展示该节点的上下游节点
const showAdjacentNodes = (node) => {
  const { from, to } = timeInfo;
  const params = {
    systemId: query.systemId,
    from,
    to,
  };
  const data = [
    {
      masterIp: node.masterIp,
      namespace: node.namespace,
      workloadKind: node.workloadKind,
      workloadName: node.workloadName,
      nodeId: node.nodeId,
    },
  ];
  singleNodeTopology(data, params).then((res) => {
    const { instances: apiNodes, edges: apiEdges } = res.data;
    const nodes = map(apiNodes, (node) => {
      return {
        ...node,
        id: node.workloadKey,
        x: node.xaxis,
        y: node.yaxis,
        inSystem:
          find(editTopoData.nodes, { workloadKey: node.workloadKey })
            ?.inSystem || false,
      };
    });
    const edges = map(apiEdges, (edge) => {
      return {
        ...edge,
        source: edge.srcWorkloadKey,
        target: edge.dstWorkloadKey,
      };
    });
    adjacentState.value = {
      ...adjacentState.value,
      visible: true,
      id: node.id,
    };
    setTimeout(() => {
      adjacentState.value.adjacentTopoData = { nodes, edges };
    });
    // this.setState({
    //   adjacentVisible: true,
    //   nowAdjacentNodeId: node.id,
    // }, () => {
    //   // modal打开时内部dom未渲染导致拓扑绘制失败，setTimeout打破异步
    //   setTimeout(() => {
    //     this.setState({ adjacentTopoData: { nodes, edges } });
    //   })
    // });
  });
};
// 回溯时间点切换
const onTimeChange = (trackBackItem) => {
  if (!topoRef?.value?.graph) {
    return;
  }
  console.log(topoRef.value, "value");

  const { startTime, endTime, topoData } = trackBackItem;
  const graph = topoRef.value.graph;
  // 更新attachDataType同环比类型，在回溯过程中可以切换同环比
  const tempTopoData = cloneDeep(topoData);
  forEach(tempTopoData.nodes, (node) => {
    node.attachDataType = attachDataType.value;
  });
  forEach(tempTopoData.edges, (edge) => {
    edge.attachDataType = attachDataType.value;
  });
  // todo 时间选择组件
  // this.setState({ queryTime: { from: startTime, to: endTime } });

  graph.read(tempTopoData);
  topoRef.value.setEdgeLineWidth(graph);
};
// 保存要加入系统的上下游节点到编辑视图
const onModalConfirm = () => {
  const {
    queryTime: { from, to },
    editTopoData,
    adjacentTopoData,
  } = this.state;
  const { nodes: editTopoNodes, edges: editTopoEdges } =
    cloneDeep(editTopoData);
  const { nodes: adjacentTopoNodes } = cloneDeep(adjacentTopoData);
  const params = {
    systemId: this.systemId,
    from,
    to,
  };
  const data = [];
  forEach(adjacentTopoNodes, (node) => {
    const editNode = find(editTopoNodes, { id: node.id });
    // 若当前节点在编辑视图中-将节点inSystem字段更新。若不在编辑视图中且inSystem为ture-节点信息push到编辑拓扑中
    if (editNode) {
      editNode.inSystem = node.inSystem;
    } else {
      if (node.inSystem) {
        editTopoNodes.push(node);
        data.push({
          masterIp: node.masterIp,
          namespace: node.namespace,
          workloadKind: node.workloadKind,
          workloadName: node.workloadName,
          nodeId: node.nodeId,
        });
      }
    }
  });
  if (data.length) {
    adjacentState.value.loading = true;
    this.setState({ modalConfirmLoading: true });
    // 获取新加入节点的上下游调用关系
    request
      .post("/system-topology/getSingleNodeTopology", data, { params })
      .then((res) => {
        const { edges: apiEdges } = res.data.data;
        // 相关调用线也要加入到编辑视图中
        forEach(apiEdges, (edge) => {
          if (
            findIndex(editTopoNodes, { id: edge.srcWorkloadKey }) > -1 &&
            findIndex(editTopoNodes, { id: edge.dstWorkloadKey }) > -1 &&
            findIndex(editTopoEdges, {
              source: edge.srcWorkloadKey,
              target: edge.dstWorkloadKey,
            }) === -1
          ) {
            editTopoEdges.push({
              ...edge,
              source: edge.srcWorkloadKey,
              target: edge.dstWorkloadKey,
            });
          }
        });
        // editTopo中节点数据被antv污染，导致深拷贝后的元素不做修改直接set，使页面卡死崩溃-删除节点中type字段可解决问题
        forEach(editTopoNodes, (node) => {
          delete node.type;
        });
        editTopoData.value = { nodes: editTopoNodes, edges: editTopoEdges };
        adjacentState.value.visible = false;
        // this.setState({
        //   editTopoData: { nodes: editTopoNodes, edges: editTopoEdges },
        //   adjacentVisible: false,
        // })
      })
      .finally(() => {
        // this.setState({ modalConfirmLoading: false });
        adjacentState.value.loading = false;
      });
  } else {
    // editTopo中节点数据被antv污染，导致深拷贝后的元素不做修改直接set，使页面卡死崩溃-删除节点中type字段可解决问题
    forEach(editTopoNodes, (node) => {
      delete node.type;
    });
    editTopoData.value = { nodes: editTopoNodes, edges: editTopoEdges };
    adjacentState.value.visible = false;
    // this.setState({
    //   editTopoData: { nodes: editTopoNodes, edges: editTopoEdges },
    //   adjacentVisible: false,
    // })
  }
};
watch(optionsData, getTopoData);
watch(isMerge, () => {
  const newTopoData = getTopoDataByIsMerge(topoData);
  topoData.value = newTopoData;
});
// watch(topoData, () => {
//   console.log('topoData change in index');

// })
onMounted(() => {
  getSystemInfoData();
  getTopoData();
  // getNamespaceClusterOptions()
});
</script>

<style scoped lang="scss">
.call-topology {
  display: flex;
  flex-direction: column;

  // padding: 12px;
  min-height: calc(100vh - 134px);

  .timer_play_warp {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
  }

  .topo-main {
    flex: 1;
  }
  .tip-button {
    :deep(.garco-modal ){
      position: absolute;
      right: 60px;
      box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.25);
    }
  }
  .topo-wrapper {
    // flex: 1;
    display: flex;
    flex-direction: column;
    height: calc(100vh - 174px);
    padding: 16px;
    border-radius: 0px 0px 4px 4px;
    border: 0.5px solid rgba(226, 232, 244, 1);
    box-sizing: border-box;
    background: rgba(255, 255, 255, 1);
  }
  :deep(.garco-tabs-type-card-gutter > .garco-tabs-content){
    border: none;
  }
}
</style>
